/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023.
 * Description: support oom_extend.
 * Author: heyuqiang <heyuqiang1@huawei.com>
 * Create: 2023-09-05
 * Note: this file isn't compiled independently, it is includded
 * in fs/namespace.c
 */
#ifdef CONFIG_RTOS_OOM_EXTEND
#include <linux/statfs.h>
#include <linux/rtos_oom_extend.h>

#define BYTE2KB(size) ((size) >> 10)
#define PERCENTAGE 100

static int get_fs_path(struct task_struct *task, struct path *path, bool root)
{
	struct fs_struct *fs;
	int result = -ENOENT;

	fs = task->fs;
	if (fs) {
		spin_lock(&fs->lock);
		*path = root ? fs->root : fs->pwd;
		path_get(path);
		spin_unlock(&fs->lock);
		result = 0;
	}
	return result;
}

static char oom_path_buff[PATH_MAX];

char *get_path(struct path *path)
{
	char *p = d_path(path, oom_path_buff, PATH_MAX);

	if (!IS_ERR(p))
		return p;

	return NULL;
}

static int can_statfs_in_oom(const char *fs_type)
{
	if (!fs_type)
		return 0;

	if (strcmp(fs_type, "tmpfs") == 0)
		return 1;
	else if (strcmp(fs_type, "squashfs") == 0)
		return 1;
	else if (strcmp(fs_type, "hugetlbfs") == 0)
		return 1;
	else if (strcmp(fs_type, "rootfs") == 0)
		return 1;
	else if (strcmp(fs_type, "ramfs") == 0)
		return 1;
	return 0;
}

void show_single_vfsmount(struct mount *mnt)
{
	struct kstatfs st;
	struct vfsmount *vfs_mount = &mnt->mnt;
	struct path mnt_path = { .dentry = vfs_mount->mnt_root, .mnt = vfs_mount };
	int err;
	char *path = NULL;
	const char *fs_type = vfs_mount->mnt_sb->s_type->name;

	if (!can_statfs_in_oom(fs_type))
		return;

	path = get_path(&mnt_path);
	if (!path)
		return;

	err = vfs_statfs(&mnt_path, &st);
	if (err)
		return;

	if (st.f_blocks)
		pr_info("%-20s  %8llu %8llu %8llu %8d%%   %-10s\n",
			mnt->mnt_devname ? mnt->mnt_devname : "none",
			BYTE2KB(st.f_blocks * (unsigned long)st.f_bsize),
			BYTE2KB((st.f_blocks - st.f_bfree)  * (unsigned long)st.f_bsize),
			BYTE2KB(st.f_bfree * (unsigned long)st.f_bsize),
			(st.f_blocks - st.f_bfree) ?
				((int)(BYTE2KB((st.f_blocks - st.f_bfree) *
				(unsigned long)st.f_bsize * PERCENTAGE))) /
				(int)(BYTE2KB(st.f_blocks * (unsigned long)st.f_bsize)) : 0,
			path);
}

int show_mount_info(void)
{
	struct task_struct *task = NULL;
	struct nsproxy *nsp = NULL;
	struct mnt_namespace *ns = NULL;
	struct path root;
	struct mount *mnt = NULL;

	task = current;

	task_lock(task);
	nsp = task->nsproxy;
	if (nsp) {
		ns = nsp->mnt_ns;
		if (unlikely(!ns)) {
			pr_err("oom extend:get mnt_namespace failed.\n");
			goto TASK_UNLOCK;
		}

		get_mnt_ns(ns);

		if (get_fs_path(task, &root, 1)) {
			pr_err("oom extend:get_mnt_ns() failed.\n");
			put_mnt_ns(ns);
			goto TASK_UNLOCK;
		}
	} else {
		pr_err("oom extend:task_nsproxy() failed.\n");
		goto TASK_UNLOCK;
	}

	task_unlock(task);

	pr_info("\nFilesystem            1K-blocks    Used   Available Use(%%)   Mounted on\n");
	if (down_read_trylock(&namespace_sem)) {
		lock_ns_list(ns);
		list_for_each_entry(mnt, &ns->list, mnt_list) {
			if (mnt_is_cursor(mnt))
				continue;
			show_single_vfsmount(mnt);
		}
		unlock_ns_list(ns);
		up_read(&namespace_sem);
	} else {
		pr_info("down_read_trylock(&namespace_sem) failed. ignore it.\n");
	}

	path_put(&root);
	put_mnt_ns(ns);

	return 0;

TASK_UNLOCK:
	task_unlock(task);
	return -EINVAL;
}
EXPORT_SYMBOL(show_mount_info);
#endif
